/*
 * fh_rtc.c
 *
 *  Created on: Aug 25, 2016
 *      Author: duobao
 */

#include "fh_rtc_v2.h"

//define here
#define SEC_BIT_START		0
#define SEC_VAL_MASK		0x3f
#define MIN_BIT_START		6
#define MIN_VAL_MASK		0xfc0
#define HOUR_BIT_START		12
#define HOUR_VAL_MASK		0x1f000
#define DAY_BIT_START		17
#define DAY_VAL_MASK		0xfffe0000


#define DAY_A_WEEK		7
#define DAY_A_MONTH		30
#define MONTH_A_YEAR	12


#define BASIC_YEAR		1970
#define BASIC_MONTH		1
#define BASIC_DAY		1


#define LEAPS_THRU_END_OF(y) ((y)/4 - (y)/100 + (y)/400)

#define  FH_RTC_INT_STATUS		0x0
#define  FH_RTC_INT_STATUS_CORE_IDLE   	(1<<5)
#define  FH_RTC_RD_DATA				0x18
#define  FH_RTC_WR_DATA				0x1C
#define  FH_RTC_CMD					0x14
#define  RTC_READ 1
#define  RTC_WRITE 2

static int fh_rtc_core_idle(unsigned int base_addr)
{
	unsigned int status;
	status = GET_REG(base_addr+FH_RTC_INT_STATUS);
	if (status & FH_RTC_INT_STATUS_CORE_IDLE)
		return 0;
	else
		return -1;
}

static int fh_rtc_core_wr(unsigned int base_addr)
{
	int reg;
	reg = GET_REG(base_addr+FH_RTC_INT_STATUS);
	reg &= (~FH_RTC_INT_STATUS_CORE_IDLE);
	SET_REG(base_addr+FH_RTC_INT_STATUS, reg);
	return 0;
}
struct fh_rtc_core_int_status {
	unsigned int core_int_en;
	unsigned int core_int_status;
};
static int fh_rtc_set_counter(unsigned int base_addr,unsigned int value)
{


	int count;
	if (fh_rtc_core_idle(base_addr) < 0) {
		printf( "set time :rtc core busy\n");
		return -1;
	}
	fh_rtc_core_wr(base_addr);
	SET_REG(base_addr+FH_RTC_WR_DATA,value);
	SET_REG(base_addr+FH_RTC_CMD, RTC_WRITE);
	for (count = 0; count < 15; count++) {
		if (fh_rtc_core_idle(base_addr) == 0)
		return 0;
		udelay(1000);
	}
	printf( "rtc core busy can't set time\n");
	return -1;

}
static unsigned int fh_rtc_get_counter(unsigned int base_addr)
{
	unsigned int tmp;

	int count, status;
	if (fh_rtc_core_idle(base_addr) < 0) {
		printf( "fh rtc get time error\n");
		return 0;
	}
	fh_rtc_core_wr(base_addr);
	SET_REG(base_addr+FH_RTC_CMD, RTC_READ);
	for (count = 0; count < 25; count++) {
		status = fh_rtc_core_idle(base_addr);
		if (status == 0) {
			tmp = GET_REG(base_addr+FH_RTC_RD_DATA);

			return tmp;

		}
		udelay(1000);

	}
	printf( "rtc core busy can't get time\n");

	return 0;

}
int fh_rtc_get_time(unsigned int base_addr)
{
	unsigned int date;
	date = fh_rtc_get_counter(base_addr);
	return date;
}



int fh_rtc_set_time(unsigned int base_addr,unsigned int value)
{
	fh_rtc_set_counter(base_addr,value);
	return 0;
}

//struct here
struct fh_rtc_controller {
	// vadd
	void * regs; /* vaddr of the control registers */
	u32 pregs; /* paddr of the control registers */

	u32 base_year;
	u32 base_month;
	u32 base_day;

};


enum {
	TIME_FUNC = 0,
	ALARM_FUNC,
};

enum {

	ISR_SEC_POS = 0,
	ISR_MIN_POS = 1,
	ISR_HOUR_POS = 2,
	ISR_DAY_POS = 3,
	ISR_ALARM_POS = 4,
	ISR_MASK_OFFSET = 27,
};



//func here
u32 rtc_month_days(unsigned int month, unsigned int year);

//g_val
static struct fh_rtc_controller g_rtc_control = {
		.regs = (void *) RTC_REG_BASE,
		.pregs = RTC_REG_BASE,
		.base_year = BASIC_YEAR,
		.base_month = BASIC_MONTH,
		.base_day = BASIC_DAY,
};

static const u32 rtc_ydays[2][13] = {
/* Normal years */
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
/* Leap years */
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } };

static const u32 rtc_days_in_month[] = {
	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};





//#define FH_RTC_DEBUG_PRI

#ifdef FH_RTC_DEBUG_PRI
#define RTC_PRINT_DBG(fmt, args...) \
	printf("[FH_RTC_DEBUG]: "); \
	printf(fmt, ## args)
#else
#define RTC_PRINT_DBG(fmt, args...)  do { } while (0)
#endif


#ifdef FH_RTC_DEBUG_PRI
static void fh_rtc_dump_regs(struct fh_rtc_controller *rtc)
{
	RTC_PRINT_DBG("--------------------------------\n");
	RTC_PRINT_DBG("cnt:0x%x\n", fh_rtc_get_time(rtc->pregs));
	RTC_PRINT_DBG("offset:0x%x\n", fh_rtc_get_offset(rtc->pregs));
	RTC_PRINT_DBG("fail:0x%x\n", fh_rtc_get_power_fail(rtc->pregs));
	RTC_PRINT_DBG("alarm_cnt:0x%x\n", fh_rtc_get_alarm_time(rtc->pregs));
	RTC_PRINT_DBG("int stat:0x%x\n", fh_rtc_get_int_status(rtc->pregs));
	RTC_PRINT_DBG("int en:0x%x\n", fh_rtc_get_enabled_interrupt(rtc->pregs));
	RTC_PRINT_DBG("sync:0x%x\n", fh_rtc_get_sync(rtc->pregs));
	RTC_PRINT_DBG("debug:0x%x\n", fh_rtc_get_debug(rtc->pregs));
	RTC_PRINT_DBG("--------------------------------\n");
}
#endif

static void fh_rtc_dump_current_data(struct rtc_time *rtc_tm)
{

	RTC_PRINT_DBG("--------------------------------\n");
	RTC_PRINT_DBG("year:\t%d\n", rtc_tm->tm_year);
	RTC_PRINT_DBG("month:\t%d\n", rtc_tm->tm_mon);
	RTC_PRINT_DBG("day:\t%d\n", rtc_tm->tm_mday);
	RTC_PRINT_DBG("hour:\t%d\n", rtc_tm->tm_hour);
	RTC_PRINT_DBG("min:\t%d\n", rtc_tm->tm_min);
	RTC_PRINT_DBG("sec:\t%d\n", rtc_tm->tm_sec);
	RTC_PRINT_DBG("--------------------------------\n");

}

static u32 is_leap_year(unsigned int year)
{
	return (!(year % 4) && (year % 100)) || !(year % 400);
}

/*************
 *
 *
 * HW PROCESS
 *
 *
 ************/

u32 rtc_year_days(unsigned int day, unsigned int month, unsigned int year)
{
	u32 temp;
	u32 old_days;
	temp = is_leap_year(year);

	old_days = ((year - 1970) * 365
		+ LEAPS_THRU_END_OF(year - 1)
		- LEAPS_THRU_END_OF(1970 - 1));

	RTC_PRINT_DBG("old_days %d of year%d\n", old_days, year);

	temp = old_days + rtc_ydays[temp][month] + day - 1;
	return temp;
}
void rtc_time_to_tm(unsigned long time, struct rtc_time *tm)
{
	unsigned int month, year;
	int days;
	int rtc_count;
	rtc_count = fh_rtc_get_time(RTC_REG_BASE);
	days = FH_GET_RTC_DAY(rtc_count);
	/* day of the week, 1970-01-01 was a Thursday */
	tm->tm_wday = (days + 4) % 7;

	year = 1970 + days / 365;
	days -= (year - 1970) * 365
		+ LEAPS_THRU_END_OF(year - 1)
		- LEAPS_THRU_END_OF(1970 - 1);
	if (days < 0) {
		year -= 1;
		days += 365 + is_leap_year(year);
	}
	tm->tm_year = year;
	tm->tm_yday = days + 1;

	for (month = 0; month < 11; month++) {
		int newdays;

		newdays = days - rtc_month_days(month, year);
		if (newdays < 0)
			break;
		days = newdays;
	}
	tm->tm_mon = month + 1;
	tm->tm_mday = days + 1;

	tm->tm_isdst = 0;

	tm->tm_hour = FH_GET_RTC_HOUR(rtc_count);
	tm->tm_min = FH_GET_RTC_MIN(rtc_count) ;
	tm->tm_sec = FH_GET_RTC_SEC(rtc_count);
}



u32 rtc_month_days(unsigned int month, unsigned int year)
{
	return rtc_days_in_month[month] + (is_leap_year(year) && month == 1);
}



static int fh_rtc_set_hw_sec_data(struct fh_rtc_controller *fh_rtc,
                struct rtc_time *rtc_tm, u32 func_switch)
{
	u32 raw_value;

	u32 sec_value;
	u32 min_value;
	u32 hour_value;
	u32 day_count;

	fh_rtc_dump_current_data(rtc_tm);

	if (rtc_tm->tm_year < 1970)	{
		printf("invalid year %d\n", rtc_tm->tm_year);
		return -1;
	}

	hour_value = rtc_tm->tm_hour;
	min_value = rtc_tm->tm_min;
	sec_value = rtc_tm->tm_sec;

	day_count = rtc_year_days(rtc_tm->tm_mday, rtc_tm->tm_mon-1,
			rtc_tm->tm_year);

	raw_value = (day_count << DAY_BIT_START)
	                | (hour_value << HOUR_BIT_START)
	                | (min_value << MIN_BIT_START)
	                | (sec_value << SEC_BIT_START);

	if (func_switch == TIME_FUNC)
		return fh_rtc_set_time(fh_rtc->pregs,raw_value);
	else
		RTC_PRINT_DBG("uboot don't support rtc-alarm!\n");

	return 0;
}



static int fh_rtc_set_hw_time_data(struct fh_rtc_controller *fh_rtc,
                struct rtc_time *rtc_tm)
{
	return fh_rtc_set_hw_sec_data(fh_rtc, rtc_tm, TIME_FUNC);
}


//uboot app call func..

int rtc_get(struct rtc_time *tmp)
{
	//first get hw data ...if year is 0...set to 1970...
	u32 temp_sec = 0;

	rtc_time_to_tm(temp_sec, tmp);
	return 0;
}

int rtc_set(struct rtc_time *tmp)
{
	return fh_rtc_set_hw_time_data(&g_rtc_control,tmp);
}

void rtc_reset(void)
{
	struct rtc_time tmp = {0};
	//set hw cnt back to 0;

	tmp.tm_year = g_rtc_control.base_year;
	tmp.tm_mon = g_rtc_control.base_month;
	tmp.tm_mday= g_rtc_control.base_day;
	fh_rtc_set_hw_time_data(&g_rtc_control,&tmp);

	return;

}
